﻿/*
Copyright (c) 2011, Pawel Szczurek
All rights reserved.


Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:


Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without
specific prior written permission.


THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Alice.VM;

namespace Alice.Script
{
    public struct StackValue
    {
       // internal StackValue() { }

        StackValue(ValueType t, int arg, double num, object obj)
        {
            m_valueType = t;
            m_valueArg = arg;
            m_valueNumeric = num;
            //m_valueString = str;
            m_valueObject = obj;
        }

        //public StackValue(IFunction f) { /*SetFunction(f); */}
        public static StackValue CreateUndefined() { return new StackValue(ValueType.Undefined, 0, 0,  null); }
        public static StackValue CreateArg(int arg) { return new StackValue(ValueType.Arg, arg, 0,  null); }
        public static StackValue CreateFunction(IAliceFunction f) { return new StackValue(ValueType.Function, 0, 0,  f); }
       // public static StackValue CreateFunction(NewFunction f) { return new StackValue(ValueType.Function, 0, 0,  f);
        public static StackValue CreateBool(bool b) { return new StackValue(ValueType.Bool, b ? 1 : 0, 0,  null); }
        public static StackValue CreateNumeric(double v) { return new StackValue(ValueType.Numeric, 0, v,  null); }
        public static StackValue CreateString(string s) { return new StackValue(ValueType.String, 0, 0, s); }
        public static StackValue CreateObject(object o) { return new StackValue(ValueType.Object, 0, 0,  o); }

       /* public void SetUndefined()
        {
            CleanValue();
            m_valueType = Script.ValueType.Undefined;
        }
        internal void SetArg(Int32 v)
        {
            CleanValue();
            m_valueArg = v;
            m_valueType = Script.ValueType.Arg;
        }
        public void SetFunction(IFunction v)
        {
            CleanValue();
            m_valueFunction = v;
            m_valueType = Script.ValueType.Function;
        }
        public void SetBool(bool v)
        {
            CleanValue();
            m_valueArg = v ? 1 : 0;
            m_valueType = Script.ValueType.Bool;
        }
        public void SetNumeric(double v)
        {
            CleanValue();
            m_valueNumeric = v;
            m_valueType = Script.ValueType.Numeric;
        }
        public void SetString(string v)
        {
            CleanValue();
            m_valueString = v;
            m_valueType = Script.ValueType.String;
        }
        public void SetObject(object v)
        {
            CleanValue();
            m_valueObject = v;
            m_valueType = Script.ValueType.Object;
        }*/

        internal int GetArg()
        {
            Debug.Assert(m_valueType == Script.ValueType.Arg);
            return m_valueArg;
        }
        public IAliceFunction GetFunction()
        {
            Debug.Assert(m_valueType == Script.ValueType.Function);
            return (IAliceFunction)m_valueObject;
        }
        public bool GetBool()
        {
            Debug.Assert(m_valueType == Script.ValueType.Bool);
            return m_valueArg == 0 ? false : true;
        }
        public double GetNumeric()
        {
            Debug.Assert(m_valueType == Script.ValueType.Numeric);
            return m_valueNumeric;
        }
        public string GetString()
        {
            Debug.Assert(m_valueType == Script.ValueType.String);
            return (string)m_valueObject;
        }
        public object GetObject()
        {
            Debug.Assert(m_valueType == Script.ValueType.Object);
            return m_valueObject;
        }

     /*   internal object ToDotNetObject()
        {
            switch (GetValueType())
            {
                //case Alice.Script.ValueType.Arg: return GetBool(); break;
                case Alice.Script.ValueType.Bool: return GetBool(); break;
                case Alice.Script.ValueType.Function: return GetFunction(); break;
                case Alice.Script.ValueType.Numeric: return GetNumeric(); break;
                case Alice.Script.ValueType.Object: return GetObject(); break;
                case Alice.Script.ValueType.String: return GetString(); break;
                // case Alice.Script.ValueType.Undefined: return GetBool(); break;
            }
            return null;
        }*/
      /*  internal void FromDotNetObject(object v)
        {
            if (v is double)
                SetNumeric((double)v);
            else if (v is int)
                SetNumeric((int)v);
            else if (v is float)
                SetNumeric((float)v);
            else if (v is string)
                SetString((string)v);
            else if (v is bool)
                SetBool((bool)v);
            else if (v is IFunction)
                SetFunction((IFunction)v);
            //else if (v is ExecutionUnit.IAliceFunction)
            //    SetFunction(((ExecutionUnit.IAliceFunction)v));
            else
                SetObject(v);
        }*/

     /*   internal void CopyDataFrom(StackValue sv)
        {
            m_valueType = sv.m_valueType;

            m_valueArg = sv.m_valueArg;
            m_valueNumeric = sv.m_valueNumeric;
            m_valueString = sv.m_valueString;
            m_valueObject = sv.m_valueObject;
            m_valueFunction = sv.m_valueFunction;
        }*/

        public Script.ValueType GetValueType()
        {
            return m_valueType;
        }

        internal static void ExecuteBinaryOperator(OPCodes op, CallStack cs)
        {
            StackValue sv1 = cs.Pop();
            StackValue sv0 = cs.Pop();
            StackValue svOut = StackValue.CreateUndefined();
            bool done = false;

            if (op == OPCodes.Equal)
            {
                if (sv0.GetValueType() != sv1.GetValueType())
                {
                    svOut = StackValue.CreateBool(false);
                    done = true;
                }
            }

          /*  if (op == OPCodes.NotEqual)
            {
                if (sv0.GetValueType() != sv1.GetValueType())
                {
                    svOut = StackValue.CreateBool(true);
                    done = true;
                }
            }*/

            if (!done)
            {

                bool showError = false;

                switch (sv0.GetValueType())
                {
                    case ValueType.Undefined :
                        switch (op)
                        {
                            case OPCodes.Equal: svOut = StackValue.CreateBool(true); break;
                        //    case OPCodes.NotEqual: svOut = StackValue.CreateBool(false); break;
                            default:
                                showError = true;
                                break;
                        }
                        break;

                    case ValueType.Arg :
                        switch (op)
                        {
                            case OPCodes.Equal: svOut = StackValue.CreateBool(sv0.GetArg() == sv1.GetArg()); break;
                        //    case OPCodes.NotEqual: svOut = StackValue.CreateBool(sv0.GetArg() != sv1.GetArg()); break;
                            default:
                                showError = true;
                                break;
                        }
                        break;

                    case Script.ValueType.Function:
                        switch (op)
                        {
                            case OPCodes.Equal: svOut = StackValue.CreateBool(sv0.GetFunction() == sv1.GetFunction()); break;
                        //    case OPCodes.NotEqual: svOut = StackValue.CreateBool(sv0.GetFunction() != sv1.GetFunction()); break;
                            default:
                                showError = true;
                                break;
                        }
                        break;

                    case Script.ValueType.Bool:
                        switch (op)
                        {
                            case OPCodes.Or: svOut = StackValue.CreateBool(sv0.GetBool() || sv1.GetBool()); break;
                            case OPCodes.And: svOut = StackValue.CreateBool(sv0.GetBool() && sv1.GetBool()); break;
                            case OPCodes.Xor: svOut = StackValue.CreateBool(sv0.GetBool() ^ sv1.GetBool()); break;
                            case OPCodes.Equal: svOut = StackValue.CreateBool(sv0.GetBool() == sv1.GetBool()); break;
                       //     case OPCodes.NotEqual: svOut = StackValue.CreateBool(sv0.GetBool() != sv1.GetBool()); break;
                            default:
                                showError = true;
                                break;
                        }
                        break;

                    case Script.ValueType.Numeric:
                        switch (op)
                        {
                            case OPCodes.Add: svOut = StackValue.CreateNumeric(sv0.GetNumeric() + sv1.GetNumeric()); break;
                            case OPCodes.Sub: svOut = StackValue.CreateNumeric(sv0.GetNumeric() - sv1.GetNumeric()); break;
                            case OPCodes.Mul: svOut = StackValue.CreateNumeric(sv0.GetNumeric() * sv1.GetNumeric()); break;
                            case OPCodes.Div: svOut = StackValue.CreateNumeric(sv0.GetNumeric() / sv1.GetNumeric()); break;
                            case OPCodes.Mod: svOut = StackValue.CreateNumeric(sv0.GetNumeric() % sv1.GetNumeric()); break;

                            case OPCodes.Equal: svOut = StackValue.CreateBool(sv0.GetNumeric() == sv1.GetNumeric()); break;
                       //     case OPCodes.NotEqual: svOut = StackValue.CreateBool(sv0.GetNumeric() != sv1.GetNumeric()); break;
                            case OPCodes.Less: svOut = StackValue.CreateBool(sv0.GetNumeric() < sv1.GetNumeric()); break;
                            case OPCodes.LessEqual: svOut = StackValue.CreateBool(sv0.GetNumeric() <= sv1.GetNumeric()); break;
                            default:
                                showError = true;
                                break;
                        }
                        break;

                    case Script.ValueType.String:
                        switch (op)
                        {
                            case OPCodes.Add: svOut = StackValue.CreateString(sv0.GetString() + sv1.GetString()); break;
                            case OPCodes.Equal: svOut = StackValue.CreateBool(sv0.GetString() == sv1.GetString()); break;
                        //    case OPCodes.NotEqual: svOut = StackValue.CreateBool(sv0.GetString() != sv1.GetString()); break;
                            default:
                                showError = true;
                                break;
                        }
                        break;

                    case Script.ValueType.Object: break;
                        switch (op)
                        {
                            default:
                                showError = true;
                                break;
                        }
                        break;

                    default:
                        showError = true;
                        break;
                }

                if (showError)
                    throw new ExecutionUnit.ExecutionUnitException("wrong operator for object", cs.CurrentIP, "");
            }

           // eu.DestroyStackValue(sv0);
           // eu.DestroyStackValue(sv1);
            cs.Push(ref svOut);
        }
        internal static void ExecuteUnaryOperator(OPCodes op, CallStack cs)
        {
            StackValue sv0 = cs.Pop();
            StackValue svOut = StackValue.CreateUndefined();// = eu.CreateStackValue();

            bool showError = false;

            switch (op)
            {
                case OPCodes.Not:
                    {
                        if (sv0.GetValueType() == Script.ValueType.Bool)
                            svOut = StackValue.CreateBool(!sv0.GetBool());
                    }
                    break;
               /* case OPCodes.PrefixMinus:
                    {
                        if (sv0.GetValueType() == Script.ValueType.Numeric)
                            svOut = StackValue.CreateNumeric(-sv0.GetNumeric());
                    }
                    break;*/
                default:
                    showError = true;
                    break;
            }

            if (showError)
                throw new ExecutionUnit.ExecutionUnitException("wrong operator for object", cs.CurrentIP, "");


            // eu.DestroyStackValue(sv0);
            cs.Push(ref svOut);
        }
     /*   internal static void ExecuteNativeFunction(StackValue32 svFunc, ExecutionUnit eu, ref bool wait, ref object waitState)
        {
            //prepare
            NativeFunctionStack32 nfs = new NativeFunctionStack32(eu.m_stack);

            //update wait
            ((ICSFunctionWait)nfs).EnableWait = wait;
            ((ICSFunctionWait)nfs).WaitState = waitState;

            //execute
            svFunc.m_valueNFunction(nfs);

            //update wait
            wait = ((ICSFunctionWait)nfs).EnableWait;
            waitState = ((ICSFunctionWait)nfs).WaitState;

            if (!wait)
            {
                //destroy arg count
                StackValue32 svArgCount = eu.Pop();
                int argCount = svArgCount.GetArg();
                eu.DestroyStackValue(svArgCount);

                //destroy arg list
                for (int i = 0; i < argCount; i++)
                    eu.DestroyStackValue(eu.Pop());

                //push return value
                StackValue32 sv = eu.CreateStackValue();

                if (nfs.ReturnValueUndefined)
                    sv.SetUndefined();
                else
                    sv.FromDotNetObject(nfs.ReturnValue);

                //push return value
                eu.Push(sv);
            }
        }*/
        internal static bool IsFalse(StackValue sv)
        {
            Debug.Assert(sv.m_valueType == Script.ValueType.Bool);
            return sv.m_valueArg == 0;
        }

        internal void CleanValue()
        {
            m_valueType = Script.ValueType.Undefined;

            //Int32 m_valueArg = 0;
            //double m_valueNumeric ;
           // m_valueString = string.Empty;
            m_valueObject = null;
          // m_valueFunction = null;
            //m_codePtr = 
        }

     /*   internal static bool AreEqual(StackValue sv0, StackValue sv1)
        {
            Debug.Assert(sv0.m_valueType == sv1.m_valueType);

            if (sv0.m_valueType == ValueType.Undefined)
                return true;

            if (sv0.m_valueType == ValueType.Arg || sv0.m_valueType == ValueType.Bool)
                return sv0.m_valueArg == sv1.m_valueArg;

            if (sv0.m_valueType == ValueType.Numeric)
                return sv0.m_valueNumeric == sv1.m_valueNumeric;

            if (sv0.m_valueType == ValueType.Function || sv0.m_valueType == ValueType.Object || sv0.m_valueType == ValueType.String)
                return sv0.m_valueObject == sv1.m_valueObject;

            throw new Exception("you should not be here");
        }*/

        #region private

     //   int m_referenceCount = 0;
        Script.ValueType m_valueType;// = Script.ValueType.Undefined;

        int m_valueArg;// = 0;
        double m_valueNumeric;
        //string m_valueString;
        object m_valueObject;
       // IFunction m_valueFunction;

        #endregion
    }
}
